const boundMap = new WeakMap();

/**
 * 更新数据
 *
 * @param el DOM 元素
 * @param bounds 数据
 * @returns {void}
 */
function updateBoundMap(el, bounds) {
  const values = boundMap.get(el);
  boundMap.set(el, { ...values, ...bounds });
}

/**
 * 拖拽指令
 *
 * @type {{mounted(*, *): void}}
 *
 * 示例：
 * <div v-drag="{ left: 100, top: 100, onMoveChange: (bounds) => {} }">Drag Me</div>
 * 或
 * <div v-drag:both="{ left: 100, top: 100, onMoveChange: (bounds) => {} }">Drag Me</div>
 *
 * 垂直：
 * <div v-drag:horizontal="{ left: 100, onMoveChange: (bounds) => {} }">Drag Me</div>
 * 水平：
 * <div v-drag:vertical="{ top: 100, onMoveChange: (bounds) => {} }">Drag Me</div>
 *
 * bounds.left 左侧距离
 * bounds.top 顶部距离
 * bounds.maxLeft 最大左侧距离 (宽度)
 * bounds.maxTop 最大顶部距离 (高度)
 *
 * 方法：
 * el.updateBounds(bounds) 更新位置
 */
export const vDrag = {
  created(el, binding) {
    const { left, top } = binding.value;
    updateBoundMap(el, { left, top });
  },
  mounted(el, binding) {
    let status = false;
    const start = { x: 0, y: 0 };
    const onCallback = binding.value?.onMoveChange;
    const direction = binding.arg || 'both'; // 默认方向为 both

    setTimeout(() => onInit(), 0);

    const onInit = () => {
      const bounds = boundMap.get(el) || { left: 0, top: 0 };
      const { left, top } = binding.value;
      bounds.left = left ?? bounds.left;
      bounds.top = top ?? bounds.top;
      const { offsetWidth, offsetHeight } = el.parentNode;
      bounds.maxLeft = offsetWidth;
      bounds.maxTop = offsetHeight;

      boundMap.set(el, bounds);

      // 更新样式
      el.style.position = 'absolute';
      onUpdateStyle(bounds);
    };

    const onUpdateStyle = (bounds) => {
      if (direction === 'both' || direction === 'horizontal') {
        el.style.left = bounds.left + 'px';
      }
      if (direction === 'both' || direction === 'vertical') {
        el.style.top = bounds.top + 'px';
      }
    };

    /**
     * 数据更新
     *
     * @param bounds 参数
     * @returns void
     */
    const onUpdate = (bounds) => {
      onUpdateStyle(bounds);
      updateBoundMap(el, bounds);
      onCallback?.(boundMap.get(el));
    };

    function onDragStart(e) {
      status = true;
      start.x = e.clientX;
      start.y = e.clientY;
    }

    function onDragMove(e) {
      if (status === false) {
        return;
      }

      const bounds = boundMap.get(el);
      const left = bounds.left + e.clientX - start.x;
      const top = bounds.top + e.clientY - start.y;

      let isUpdate = false;
      if (direction === 'both' || direction === 'horizontal') {
        if (left >= 0 && left <= bounds.maxLeft) {
          start.x = e.clientX;
          bounds.left = left;
          isUpdate = true;
        }
      }

      if (direction === 'both' || direction === 'vertical') {
        if (top >= 0 && top <= bounds.maxTop) {
          start.y = e.clientY;
          bounds.top = top;
          isUpdate = true;
        }
      }

      isUpdate && onUpdate(bounds);
    }

    function onDragEnd() {
      status = false;
    }

    // 暴露方法
    el.updateBounds = onUpdate;
    // 绑定事件
    el.addEventListener('mousedown', onDragStart);
    document.addEventListener('mousemove', onDragMove);
    document.addEventListener('mouseup', onDragEnd);
  }
};
